<!--
$emit change发送出来的参数有
{ value, _uid, type }
@param {string, array} value 当前表的数据
@param {string, array} _uid 当前编辑行的_uid,移动时，互动两个行的_uid
@param {string} type 操作类型
  add_line - 弹窗新增行
  edit_line - 弹窗编辑整行
  delete_line - 删除行
  add_reserve - 新增空白行
  edit_${col.name} - 编辑行中的列，后缀即列名
  sort_${flag} 调整顺序的方向，后缀表示 f - 上移 b - 下移
-->
<template>
  <div :id="'table-' + field.name">
    <q-markup-table v-if="field.skeleton">
      <thead>
        <tr v-if="field.label">
          <th colspan="100%">
            <div class="q-table__title text-left text-grey">{{ field.label }}</div>
          </th>
        </tr>
        <tr>
          <th class="text-left" style="width: 150px">
            <q-skeleton animation="blink" type="text" />
          </th>
          <th class="text-right">
            <q-skeleton animation="blink" type="text" />
          </th>
          <th class="text-right">
            <q-skeleton animation="blink" type="text" />
          </th>
          <th class="text-right">
            <q-skeleton animation="blink" type="text" />
          </th>
          <th class="text-right">
            <q-skeleton animation="blink" type="text" />
          </th>
          <th class="text-right">
            <q-skeleton animation="blink" type="text" />
          </th>
        </tr>
      </thead>

      <tbody>
        <tr v-for="n in 5" :key="n">
          <td class="text-left">
            <q-skeleton animation="blink" type="text" width="85px" />
          </td>
          <td class="text-right">
            <q-skeleton animation="blink" type="text" width="50px" />
          </td>
          <td class="text-right">
            <q-skeleton animation="blink" type="text" width="35px" />
          </td>
          <td class="text-right">
            <q-skeleton animation="blink" type="text" width="65px" />
          </td>
          <td class="text-right">
            <q-skeleton animation="blink" type="text" width="25px" />
          </td>
          <td class="text-right">
            <q-skeleton animation="blink" type="text" width="85px" />
          </td>
        </tr>
      </tbody>
    </q-markup-table>

    <q-table
      v-if="!field.skeleton"
      :rows="tableData"
      :columns="columns"
      row-key="_uid"
      :grid="$q.screen.xs || field.grid"
      :rows-per-page-options="[0]"
      v-model:pagination="pagination"
      ref="table"
      :selection="selection"
      v-model:selected="field.selected"
      :virtual-scroll="!!field.scrollStyle"
      :style="field.scrollStyle || ''"
      :visible-columns="visibleColumns"
      v-model:expanded="expanded"
      :separator="field.separator || 'horizontal'"
      :dense="!!field.dense"
      :flat="!!field.flat"
      :bordered="!!field.bordered"
      :hide-bottom="!!field.hideBottom"
      :hide-header="!!field.hideHeader"
    >
      <template v-slot:top-left v-if="field.visibleId || field.label || field.canExpand">
        <div class="flex">
          <slot name="top-left">
            <div class="flex items-center">
              <div class="q-table__title q-mr-md">{{field.label}}</div>
              <q-select
                label="选择显示列"
                v-model="visibleColumns"
                multiple
                borderless
                dense
                options-dense
                :display-value="visibleColumns.length + '/' + columns.length + '列'"
                emit-value
                map-options
                :options="columns"
                option-value="name"
                :style="{'min-width': $q.screen.xs ? '100px' : '150px'}"
                v-if="field.visibleId"
              />
            </div>
          </slot>

          <q-btn-group class="q-ml-md" outline>
            <q-btn outline color="green" dense label="展开" @click="expandAll(true)" v-if="field.canExpand" />
            <q-btn outline color="orange" dense label="收起" @click="expandAll(false)" v-if="field.canExpand" />
          </q-btn-group>

        </div>
      </template>

      <template v-slot:top-right>
        <slot name="top-right">
          <div class="q-gutter-sm" v-if="field.editmode.includes('new')">
            <q-btn label="新增明细" :icon="$q.screen.xs ? undefined : 'playlist_add'" color="secondary" outline @click="addLine">
              <q-tooltip>新增明细行</q-tooltip>
            </q-btn>
            <q-btn label="新增空白" :icon="$q.screen.xs ? undefined : 'add_circle_outline'" color="indigo" outline @click="addReserveLine">
              <q-tooltip>新增空白行</q-tooltip>
            </q-btn>
          </div>
        </slot>
        <!-- 额外增加一个按钮 -->
        <div v-if="field.btn">
          <q-btn
            v-for="(item, idx) in this.additionalBtn(field.btn)"
            :key="idx"
            class="q-ml-sm"
            :label="item.label || undefined"
            :icon="item.icon || undefined"
            :color="item.color || 'primary'"
            @click="item.fn"
            :disable="!!item.disable"
          />
        </div>

        <div v-if="($q.screen.xs || field.grid) && selection === 'multiple'">
          <q-checkbox label="全选" v-model="itemSelectAll" />
        </div>
      </template>

      <template v-slot:header-selection="scope" v-if="selection === 'multiple'">
        <q-checkbox v-model="scope.selected" />
      </template>

      <!-- 大屏端 -->
      <template v-slot:body="props">
        <q-tr :props="props">
          <q-td v-if="selection != 'none'"><q-checkbox v-model="props.selected" /></q-td>
          <q-td class="q-gutter-sm" v-if="field.editmode.includes('edit') || field.editmode.includes('delete')">
            <slot name="body-operate" v-bind:props="props">
              <q-btn round color="secondary" icon="edit" size="sm" @click="editLine(props.row._uid)" v-if="field.editmode.includes('edit')">
                <q-tooltip>修改明细行</q-tooltip>
              </q-btn>
              <q-btn round color="grey" icon="delete" size="sm" @click="showDelete(props.row._uid)" v-if="field.editmode.includes('delete')">
                <q-tooltip>删除明细行</q-tooltip>
              </q-btn>

              <q-btn-group rounded outline v-if="field.sort && field.editmode.includes('edit')">
                <q-btn round outline color="accent" icon="keyboard_arrow_up" size="sm" @click="changeSort(props.rowIndex, 'f')">
                  <q-tooltip content-class="bg-accent">上移</q-tooltip>
                </q-btn>
                <q-btn round outline color="accent" icon="keyboard_arrow_down" size="sm" @click="changeSort(props.rowIndex, 'b')">
                  <q-tooltip content-class="bg-accent">下移</q-tooltip>
                </q-btn>
              </q-btn-group>
            </slot>
            <slot name="body-operate-append" v-bind:props="props">
            </slot>
          </q-td>
          <q-td :props="props" v-for="col in props.cols.filter(c => c.name != 'operate')" :key="col.name" :style="col.style" :class="col.classes">
            <slot :name="'body-'+col.name" v-bind:props="props">
              <q-img
                v-if="col.isImage && (typeof col.value) !== 'object'"
                :src="col.value"
                spinner-color="white"
                style="width: 32px;"
                :ratio="1"
              />
              <viewer v-if="col.isImage && (typeof col.value) == 'object' && col.value.length > 0" :images="[col.value[0]]" class="flex" :options="options" style="display: inline-block;">
                <template v-slot="scope">
                  <div class="image-box-xs" v-for="img in scope.images" :key="img.thumb">
                    <img class="fit-thumb" :src="img.thumb" :data-src="img.image" />
                    <!-- <div class="image-title text-center" v-if="img.title">{{img.title}}</div> -->
                  </div>
                </template>
              </viewer>
              {{ !col.isImage ? col.value : '' }}
            </slot>

            <q-popup-edit
              v-model="editFields[col.field].value"
              v-if="canPopupEdit(col, props.row)"
              :title="col.label"
              buttons
              label-set="确定"
              label-cancel="取消"
              @save="savePopupEdit(props.row, col)"
              @show="initPopupEdit(props.row, col)"
              @hide="hidePopupEdit(props.row, col)"
              v-slot="scope"
            >
              <fieldInput
                v-if="col.editConfig.type === 'input'"
                :field="editFields[col.field]"
                :ref="col.field"
                v-model="scope.value"
              />
              <filterSelect
                v-if="col.editConfig.type === 'select'"
                :field="editFields[col.field]"
                :ref="col.field"
                v-model="scope.value"
              />
              <dateInput
                v-if="col.editConfig.type === 'date'"
                :field="editFields[col.field]"
                :ref="col.field"
                v-model="scope.value"
              />
              <monthInput
                v-if="col.editConfig.type === 'month'"
                :field="editFields[col.field]"
                :ref="col.field"
                v-model="scope.value"
              />
              <autocomplete
                v-if="col.editConfig.type === 'autocomplete'"
                :field="editFields[col.field]"
                :ref="col.field"
                v-model="scope.value"
              />
              <fieldImage
                v-if="col.editConfig.type === 'image'"
                :field="editFields[col.field]"
                :ref="col.field"
                v-model="scope.value"
              />
            </q-popup-edit>
            <q-icon v-if="canPopupEdit(col, props.row)" name="mode_edit" color="blue-6" />
            <!-- 显示有效性内容 -->
            <div v-if="rowModel[props.key][col.field] && rowModel[props.key][col.field]._valid" class="valid">{{rowModel[props.key][col.field]._valid}}</div>
            <!-- 显示新旧对比内容 -->
            <div v-if="canShowCompare(col.field)" class="compare text-bold" v-html="compare(props.row, col.field)"></div>
          </q-td>
        </q-tr>

        <slot name="body-expand-tr" v-bind:props="props" v-if="field.canExpand">
        </slot>
      </template>

      <template v-slot:bottom-row>
        <q-tr v-for="(row, idx) in field.footer" :key="idx"  class="bg-yellow-2 text-center text-bold">
          <q-td v-for="(col, cid) in row" :key="cid">
            {{col}}
          </q-td>
        </q-tr>
      </template>
      <!-- 小屏端 -->
      <template v-slot:item="props">
        <slot name="item" v-bind:props="props">
          <q-list dense class="fit shadow-1 q-mb-md q-py-md" :class="props.selected ? 'bg-grey-2' : ''" :style="props.selected ? 'transform: scale(0.98);' : ''">
            <div v-if="selection !== 'none'">
              <q-item class="justify-center">
                <q-checkbox v-model="props.selected" label="选择" />
              </q-item>
              <q-separator />
            </div>
            <q-item v-for="col in props.cols" :key="col.name">
              <q-item-section class="text-grey-8">
                <q-item-label>{{ col.label }}</q-item-label>
              </q-item-section>
              <q-item-section style="max-width: 75%; min-width: 50%;">
                <q-item-label v-if="col.name!=='operate'" class="full-width">
                  <slot :name="'body-'+col.name" v-bind:props="props">
                    <q-img
                      v-if="col.isImage && (typeof col.value) != 'object'"
                      :src="col.value"
                      spinner-color="white"
                      style="width: 32px"
                      :ratio="1"
                    />
                    <viewer v-if="col.isImage && (typeof col.value) == 'object'" :images="[col.value[0]]" class="flex" :options="options" style="display: inline-block;">
                      <template v-slot="scope">
                        <div class="image-box-xs" v-for="img in scope.images" :key="img.thumb">
                          <img class="fit-thumb" :src="img.thumb" :data-src="img.image" />
                        <!-- <div class="image-title text-center" v-if="img.title">{{img.title}}</div> -->
                        </div>
                      </template>
                    </viewer>
                    {{ !col.isImage ? col.value : ''}} &nbsp;
                  </slot>
                  <q-popup-edit
                    v-model="editFields[col.field].value"
                    v-if="canPopupEdit(col, props.row)"
                    :title="col.label"
                    buttons
                    label-set="确定"
                    label-cancel="取消"
                    @save="savePopupEdit(props.row, col)"
                    @show="initPopupEdit(props.row, col)"
                    @hide="hidePopupEdit(props.row, col)"
                    v-slot="scope"
                  >
                    <fieldInput
                      v-if="col.editConfig.type==='input'"
                      :field="editFields[col.field]"
                      :ref="col.field"
                      v-model="scope.value"
                    />
                    <filterSelect
                      v-if="col.editConfig.type==='select'"
                      :field="editFields[col.field]"
                      :ref="col.field"
                      v-model="scope.value"
                    />
                    <dateInput
                      v-if="col.editConfig.type==='date'"
                      :field="editFields[col.field]"
                      :ref="col.field"
                      v-model="scope.value"
                    />
                    <monthInput
                      v-if="col.editConfig.type==='month'"
                      :field="editFields[col.field]"
                      :ref="col.field"
                      v-model="scope.value"
                    />
                    <autocomplete
                      v-if="col.editConfig.type === 'autocomplete'"
                      :field="editFields[col.field]"
                      :ref="col.field"
                      v-model="scope.value"
                    />
                    <fieldImage
                      v-if="col.editConfig.type === 'image'"
                      :field="editFields[col.field]"
                      :ref="col.field"
                      v-model="scope.value"
                    />
                  </q-popup-edit>
                  <q-icon v-if="canPopupEdit(col, props.row)" name="mode_edit" color="blue-6" />
                  <!-- 显示有效性内容 -->
                  <div v-if="rowModel[props.key][col.field] && rowModel[props.key][col.field]._valid" class="valid">{{rowModel[props.key][col.field]._valid}}</div>
                  <!-- 显示新旧对比内容 -->
                  <div v-if="canShowCompare(col.field)" class="compare text-bold" v-html="compare(props.row, col.field)"></div>
                </q-item-label>
                <div v-else class="q-gutter-sm">
                  <slot name="body-operate" v-bind:props="props">
                    <q-btn round color="secondary" icon="edit" size="sm" @click="editLine(props.row._uid)" v-if="field.editmode.includes('edit')" />
                    <q-btn round color="grey" icon="delete" size="sm" @click="showDelete(props.row._uid)" v-if="field.editmode.includes('delete')" />

                    <q-btn-group rounded outline v-if="field.sort && field.editmode.includes('edit')">
                      <q-btn round outline color="accent" icon="keyboard_arrow_up" size="sm" @click="changeSort(props.rowIndex, 'f')" />
                      <q-btn round outline color="accent" icon="keyboard_arrow_down" size="sm" @click="changeSort(props.rowIndex, 'b')" />
                    </q-btn-group>
                  </slot>
                  <slot name="body-operate-append" v-bind:props="props">
                  </slot>
                </div>
              </q-item-section>
            </q-item>
          </q-list>

          <div v-if="field.footer && tableData.length > 0 && props.rowIndex === tableData.length - 1" class="full-width">
            <q-list dense class="fit shadow-1 bg-yellow-2" v-for="(f, i) in field.footer" :key="i">
              <q-item v-for="(item, idx) in footerColumns(f)" :key="idx">
                <q-item-section v-for="col in item" :key="col">
                  <q-item-label>{{col}}</q-item-label>
                </q-item-section>
              </q-item>
            </q-list>
          </div>
        </slot>
        <slot name="body-expand-item" v-bind:props="props" v-if="field.canExpand">
        </slot>
      </template>

    </q-table>

    <q-dialog v-model="pop" persistent>
      <q-card style="width: 350px; max-width: 90vw;">
        <q-card-section class="row items-center q-pb-none">
          <div class="text-h6">{{editFields._uid?'修改':'新增'}}</div>
          <q-space />
          <q-btn icon="close" flat round dense v-close-popup />
        </q-card-section>

        <q-card-section>
          <q-form ref="detailForm">
            <div class="row justify-center" v-for="(field, idx ) in editFields" :key="idx">
              <div class="col-12 q-mb-md" v-if="field.type==='input'" v-show="!field.hidden">
                <fieldInput :field="field" :ref="field.field" @focus="() => field._canRender = true" @blur="() => field._canRender = false" />
              </div>
              <div class="col-12 q-mb-md" v-if="field.type==='select'">
                <filterSelect :field="field" :ref="field.ref" />
              </div>
              <div class="col-12 q-mb-md" v-if="field.type==='date'">
                <dateInput :field="field" :ref="field.ref" />
              </div>
              <div class="col-12 q-mb-md" v-if="field.type==='month'">
                <monthInput :field="field" :ref="field.ref" />
              </div>
              <div class="col-12 q-mb-md" v-if="field.type === 'autocomplete'">
                <autocomplete :field="field" :ref="field.ref" />
              </div>
              <div class="col-12 q-mb-md" v-if="field.type === 'image'">
                <fieldImage :field="field" :ref="field.ref" />
              </div>
            </div>
          </q-form>
        </q-card-section>

        <q-card-actions align="right">
          <q-btn flat label="取消" color="primary" v-close-popup />
          <q-btn label="确定" color="primary" @click="confirmLine"/>
        </q-card-actions>
      </q-card>
    </q-dialog>

    <q-dialog v-model="confirm" persistent>
      <q-card style="width: 300px">
        <q-card-section>
          <div class="text-h6">删除</div>
        </q-card-section>

        <q-card-section class="q-pt-none">
          确认删除该明细行吗？
        </q-card-section>

        <q-card-actions align="right">
          <q-btn flat label="取消" color="primary" v-close-popup />
          <q-btn label="确定" color="primary" v-close-popup @click="deleteLine" />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </div>
</template>

<script>
import { defineComponent } from 'vue';
import fieldInput from 'src/components/FieldInput.vue';
import filterSelect from 'src/components/FilterSelect.vue';
import dateInput from 'src/components/DateInput.vue';
import { uid } from 'quasar';
import monthInput from 'src/components/MonthInput.vue';
import { useStore } from 'stores/store';
import autocomplete from 'src/components/Autocomplete.vue';
import { mapState, mapActions } from 'pinia';
import fieldImage from 'src/components/FieldImage.vue';

export default defineComponent({
  name: 'detailTable',
  components: {
    fieldInput,
    filterSelect,
    dateInput,
    monthInput,
    autocomplete,
    fieldImage
  },
  props: {
    field: {
      type: Object,
      default: () => []
    }
  },
  emits: ['init', 'change'],
  data () {
    const $store = useStore();
    return {
      dialogMode: '',
      pop: false,
      confirm: false,
      deleteUID: '',
      dataset: [],
      editFields: {},
      rowModel: {}, // 数据行的字段模型对象
      pagination: { rowsPerPage: 0 },
      options: {
        zIndex: 9999,
        url: 'data-src'
      },
      visibleColumns: [],
      expanded: [],
      $store
    }
  },
  created () {
    // 对editFields初始化
    const self = this;
    this.field.columns.forEach(col => {
      if (!self.editFields[col.field]) {
        self.editFields[col.field] = { ref: col.field, value: null };
      }
    });
    this.field.editmode = this.field.editmode ? this.field.editmode : ['new', 'edit', 'delete'];
  },
  mounted () {
    const allColName = this.columns.map(v => v.name);
    if (!this.field.visibleId) {
      this.visibleColumns = allColName;
    } else {
      let visCols = this.$q.localStorage.getItem(this.storageKey) || allColName;

      visCols = visCols.filter(v => allColName.includes(v));
      this.visibleColumns = visCols;
    }
  },
  methods: {
    addLine () { // 新增明细行
      this.initEditFields();
      if (typeof this.field.modal == 'function') { // 执行预定义的弹窗前函数
        this.parseModalResult(this.field.modal('add', null));
      }
      this.pop = true;
    },
    editLine (_uid) { // 修改明细行
      const row = this.tableData.filter(v => v._uid === _uid)[0];
      // this.resetEditFields(row); // 貌似多余 还影响数据准确性
      this.initEditFields(row);
      if (typeof this.field.modal == 'function') { // 执行预定义的弹窗前函数
        this.parseModalResult(this.field.modal('edit', row));
      }
      this.pop = true;
    },
    parseModalResult (ret) {
      /* 解析返回的对象，结果为
      {
        fieldname: { // 字段名
          props: value // 属性及值
        }
      }
      */
      if (Object.prototype.toString.call(ret) === '[object Object]') {
        Object.keys(ret).forEach(col => {
          if (this.editFields[col]) {
            Object.keys(ret[col]).forEach(prop => {
              this.editFields[col][prop] = ret[col][prop];
            });
          }
          const colObject = this.field.columns.filter(f => f.field === col)
          if (colObject.length > 0) {
            Object.keys(ret[col]).forEach(prop => {
              if (colObject[0].editConfig) {
                colObject[0].editConfig[prop] = ret[col][prop];
              }
            });
          }
        });
      }
    },
    waitUploadImages () { // 等待上传的图片
      // 找出类型为image的列
      const imageFields = [];
      for (const col of this.field.columns) {
        if (col.editConfig && col.editConfig.type === 'image') {
          imageFields.push(col.field);
        }
      }

      const waitUpload = []; // 待上传图片的数组
      if (imageFields.length > 0) {
        // 检查数据中是否有未上传的图片，若取出
        for (const row of this.tableData) {
          for (const col of imageFields) {
            const fieldValue = row[col] && typeof row[col] === 'string' ? JSON.parse(row[col]) : row[col];

            for (const img of fieldValue) {
              if (img.oriFile) {
                waitUpload.push({
                  _uid: row._uid,
                  field: col,
                  batch: img.batch,
                  name: img.oriFile.file.name,
                  file: img.oriFile.file
                });
              }
            }
          }
        }
      }
      return waitUpload;
    },
    updateImages () { // 上传表格里的图片
      const waitUpload = this.waitUploadImages();

      if (waitUpload.length > 0) {
        const formdata = new FormData();
        for (const item of waitUpload) {
          formdata.append(item.name, item.file);
        }

        const config = {
          headers: { 'Content-Type': 'multipart/form-data' }
          /* onUploadProgress: progressEvent => {
            if (progressEvent.lengthComputable) {
              self.step = progressEvent.loaded;
              self.max = progressEvent.total;
            }
          } */
        };

        return new Promise((resolve, reject) => {
          this.$axios.post('/upload', formdata, config).then(({ data }) => {
            for (const img of data.images) {
              const imgItem = waitUpload.find(v => v.name === img.filename);

              if (imgItem) {
                const fieldValue = this.tableData.find(v => v._uid === imgItem._uid)[imgItem.field];
                const valueImg = fieldValue.find(v => v.batch === imgItem.batch);
                if (valueImg) {
                  valueImg.image = img.url;
                  valueImg.thumb = img.thumb;

                  valueImg.file && delete valueImg.file;
                  valueImg.oriFile && delete valueImg.oriFile;
                  valueImg.canCompress !== undefined && delete valueImg.canCompress;
                  valueImg.compress !== undefined && delete valueImg.compress;
                }
              }
            }

            this.field.value = JSON.stringify(this.tableData);
            resolve({ name: this.field.name || this.field.ref, value: this.field.value });
          }).catch(err => {
            console.log(err);
            reject(err);
          });
        });
      }
    },
    async confirmLine () { // 新增或修改明细行点击确认后
      const self = this;
      // select组件validate
      for (const col in this.editFields) {
        if (this.editFields[col].type === 'select') {
          const component = this.$refs[this.editFields[col].ref || this.editFields[col].field];
          if (Object.prototype.toString.call(component) === '[object Array]') {
            for (const com of component) {
              const validate = await com.$refs.select.validate();
              if (!validate) return;
            }
          } else {
            const validate = await component.$refs.select.validate();
            if (!validate) return;
          }
        }
      }

      this.$nextTick(() => {
        this.$refs.detailForm.validate().then(outcome => {
          if (outcome) {
            if (this.editFields._uid) { // 修改
              const editRow = this.tableData.filter(v => v._uid === this.editFields._uid)[0];
              const dirtyRow = { ...editRow };
              this.field.columns.forEach(col => {
                if (this.editFields[col.field]) {
                  dirtyRow[col.field] = this.editFields[col.field].value;
                }
              });
              if (!this.checkUnique(dirtyRow)) return;
              for (const k in editRow) { // 数据唯一性检查通过后，新数据赋值给老数据
                editRow[k] = dirtyRow[k];
              }
              this.field.value = JSON.stringify(this.tableData);// 数据填回接口的field
              this.$emit('change', { value: this.field.value, uid: this.editFields._uid, type: 'edit_line' }); // 通知改变
            } else { // 新增
              const newData = { _uid: uid() };
              this.field.columns.forEach(col => {
                if (this.editFields[col.field]) {
                  newData[col.field] = this.editFields[col.field].value;
                }
              });
              if (!this.checkUnique(newData)) return;
              // 数据唯一性检查通过后，把新的数据行模型添加至数据行模型中
              this.editFields._uid = newData._uid;
              this.rowModel[newData._uid] = Object.assign({}, this.editFields);

              this.field.value = JSON.stringify([...this.tableData, newData]);// 数据填回接口的field
              this.$emit('change', { value: this.field.value, uid: newData._uid, type: 'add_line' }); // 通知改变
            }
            this.pop = false;
            // 保存后要恢复成空值，否则下一次同一列，设同一个值时，不会触发保存
            this.resetEditFields();
          }
        });
      });
    },
    showDelete (_uid) {
      this.confirm = true;
      this.deleteUID = _uid;
    },
    deleteLine () { // 删数据行及模型
      const _uid = this.deleteUID;
      const oriData = this.tableData.filter(v => v._uid == _uid);
      if (typeof this.field.value === 'string') {
        this.field.value = JSON.stringify(this.tableData.filter(v => v._uid != _uid));
      } else {
        this.field.value = this.tableData.filter(v => v._uid != _uid);
      }

      delete this.rowModel[_uid];
      this.$emit('change', { value: this.field.value, uid: oriData[0], type: 'delete_line' }); // 通知改变
    },
    addReserveLine () { // 新增空白行
      this.initEditFields();
      if (typeof this.field.modal == 'function') { // 执行预定义的弹窗前函数
        this.parseModalResult(this.field.modal('edit', null));
      }

      // 创建有_uid的空白行数据
      const newData = { _uid: uid() };
      this.field.columns.forEach(col => {
        if (this.editFields[col.field]) {
          newData[col.field] = this.editFields[col.field].value || '';
        }
      });
      this.editFields._uid = newData._uid;
      this.rowModel[newData._uid] = Object.assign({}, this.editFields);

      if (typeof this.field.value === 'string') {
        this.field.value = JSON.stringify([...this.tableData, newData]);
      } else {
        this.field.value = [...this.tableData, newData];
      }
      this.$emit('change', { value: this.field.value, uid: newData._uid, type: 'add_reserve' }); // 通知改变
    },
    checkUnique (checkrow) { // 检查唯一性 字段里的unique可两种数据类型 Boolean-只检查当前字段 Array-检查该数组里指定的字段
      for (const col of this.field.columns) {
        if (col.unique) {
          let ret = [];
          // eslint-disable-next-line no-inner-declarations
          function unique (uniqueArr, checkrow, row) {
            return uniqueArr.map(v => checkrow[v] == row[v] ? 1 : 0).reduce((a, b) => a + b) === uniqueArr.length;
          }

          if (Object.prototype.toString.call(col.unique) === '[object Boolean]') {
            ret = this.tableData.filter(row => row._uid != checkrow._uid && checkrow[col.field] == row[col.field]);
          } else if (Object.prototype.toString.call(col.unique) === '[object Array]') {
            ret = this.tableData.filter(row => row._uid != checkrow._uid && unique(col.unique, checkrow, row));
          }

          if (ret.length > 0) {
            this.$q.notify({
              message: `${col.label} ${checkrow[col.field]} 已重复，请调整`,
              color: 'negative',
              icon: 'error',
              position: 'top',
              timeout: 2000
            });
            return false;
          }
        }
      }
      return true;
    },
    initEditFields (val) { // 初始化编辑行的内容，，
      /**
       * 修改时，在rowModel取已存在的数据行模型赋值给 editFields
       * 新增时，创建新的数据行模型;赋值给 editFields，但未放入rowModel，等待确认后放入rowModel
      */
      if (val) {
        this.editFields = this.rowModel[val._uid];
      } else {
        const fields = {};
        this.field.columns.forEach(item => {
          // 新增行时，赋值默认值
          let defaultValue = null;
          if (item.editConfig && item.editConfig.default !== undefined) {
            if (typeof item.editConfig.default === 'function') {
              defaultValue = item.editConfig.default();
            } else {
              defaultValue = item.editConfig.default;
            }
          }

          if (item.editConfig) {
            fields[item.field] = {
              label: item.label,
              ref: item.field,
              ...item.editConfig,
              value: defaultValue,
              _valid: ''
            };
          }
        });
        // this.editFields[item.field] = {...}，这种形式，v-model不会赋值，改成现在这种深拷贝后才会有效
        this.editFields = Object.assign({ _uid: '' }, fields);
      }

      for (const key in this.editFields) {
        const keyField = this.editFields[key];
        // 需要数据预渲染的
        if (keyField.preRender && val) {
          if (keyField.type === 'select') {
            if (!keyField.options) {
              keyField.options = [];
            }

            // 把有字典取出放入options
            if (keyField.dict && keyField.options.length === 0) {
              keyField.options = JSON.parse(JSON.stringify(this.dictionary[keyField.dict] || '[]'));
            }
            // 过滤
            if (keyField.options.length > 0) {
              const selected = keyField.options.filter(v => v.value == val[keyField.valueField || key]);
              keyField.dataRender(this.editFields, selected);
            }
          } else {
            keyField.dataRender(this.editFields, val[key]);
          }
        }

        // 查后缀为需要计算的
        if (this.editFields[key].suffix) {
          this.editFields[key].suffix = this.parseSuffix(this.editFields[key].suffix, val);
        }
        if (this.editFields[key].prefix) {
          this.editFields[key].prefix = this.parseSuffix(this.editFields[key].prefix, val);
        }
      }
      this.$emit('init', { field: this.editFields, val });
    },
    refreshSuffix () {
      const self = this;
      // 数据
      const tempData = Object.keys(this.editFields).reduce((data, key) => {
        data[key] = self.editFields[key].value;
        return data;
      }, {});

      // 单位定义
      const suffixDefine = {};
      const prefixDefine = {};
      for (const col of this.field.columns) {
        if (col.editConfig && col.editConfig.suffix) {
          suffixDefine[col.name] = col.editConfig.suffix;
        }
        if (col.editConfig && col.editConfig.prefix) {
          prefixDefine[col.name] = col.editConfig.prefix;
        }
      }

      for (const key in this.editFields) {
        if (suffixDefine[key]) {
          this.editFields[key].suffix = this.parseSuffix(suffixDefine[key], tempData);
        }
        if (prefixDefine[key]) {
          this.editFields[key].prefix = this.parseSuffix(prefixDefine[key], tempData);
        }
      }
    },
    parseSuffix (suffix, row) { // 解析后缀
      if (suffix && suffix.indexOf('{') > -1 && suffix.indexOf('}') > -1) { // 计算型的，例如 {unit}||'个'，{}表示取字段unit的值，若无则为"个"
        const suffixArr = suffix.split('||');
        if (row) {
          const keyfields = suffixArr[0].match(/\{(.+?)\}/g); // 找出所有花括号包着的字段
          keyfields.forEach(v => {
            const strVal = row[v.substr(1, v.length - 2)];
            suffixArr[0] = suffixArr[0].replace(new RegExp(v, 'g'), strVal);
          });
          return suffixArr[0];
        } else {
          if (suffixArr.length > 1) {
            return suffixArr[1];
          } else {
            return '';
          }
        }
      } else { // 固定值类型的
        return suffix;
      }
    },
    savePopupEdit (row, col) { // 行内编辑，点击【设置】后
      const editRow = row; // 数据行对象
      const dirtyRow = { ...editRow };

      for (const key in this.editFields) {
        if (this.editFields[key].value !== undefined) {
          dirtyRow[key] = this.editFields[key].value;
        }
      }

      // 检查unique
      if (this.checkUnique(dirtyRow)) { // 统一性检查未通过
        for (const key in editRow) {
          editRow[key] = dirtyRow[key]
        }
        if (typeof this.field.value === 'string') {
          this.field.value = JSON.stringify(this.tableData);
        } else {
          this.field.value = this.tableData;
        }

        this.$emit('change', { value: this.field.value, uid: editRow._uid, type: `edit_${col.name}` }); // 通知改变
      }
    },
    initPopupEdit (row, col) { // 初始化行内编辑的弹窗
      this.initEditFields(row);
      if (typeof this.field.modal == 'function') { // 执行预定义的弹窗前函数
        this.parseModalResult(this.field.modal('edit', row));
      }

      // 恢复初始值
      for (const key in row) {
        if (this.editFields[key] && this.editFields[key].value !== undefined) {
          this.editFields[key].value = row[key];
        }
      }

      this.editFields[col.field]._canRender = true;

      // input全选
      if (col.editConfig && col.editConfig.type === 'input') {
        this.$nextTick(() => {
          this.$refs[col.field][0].$refs.input.select();
        });
      }
      // select要focus一下，否则会显示value而不会显示label
      if (col.editConfig && col.editConfig.type === 'select') {
        this.$nextTick(() => {
          const select = this.$refs[col.field][0];
          select.$refs.select.focus();
        });
      }
    },
    hidePopupEdit (row, col) {
      this.editFields[col.field]._canRender = false;
      // 保存后要恢复成空值，否则下一次同一列，设同一个值时，不会触发保存
      this.resetEditFields();
    },
    resetEditFields (val) {
      const self = this;
      if (val) {
        Object.keys(this.editFields).forEach(key => {
          if (typeof self.editFields[key] === 'object' && val[key] !== undefined) {
            self.editFields[key].value = val[key];
          }
        });
      } else {
        Object.keys(this.editFields).forEach(key => {
          if (typeof self.editFields[key] === 'object') {
            self.editFields[key].value = null;
          }
        });
      }
    },
    initRowModel (row) {
      /** 初始化数据行模型，结构为:
       * rowModel = {
       *     "xxxx-xxxx....": {     // _uid值
       *         _uid: 'xxxx-xxxx....',
       *         fieldname: {    // 具体字段名
       *             label:'',
       *             ...        // editConfig里的内容
       *         }
       *     }
       * }
        */

      function checkRule (rules, val, row) {
        const error = [];
        for (const rule of rules) {
          const err = rule(val, row);
          if (err !== true) {
            error.push(err);
          }
        }
        return error.join(',');
      }

      if (!this.rowModel[row._uid]) {
        this.rowModel[row._uid] = { _uid: row._uid };
        this.field.columns.forEach(item => {
          if (item.editConfig) {
            this.rowModel[row._uid][item.field] = {
              label: item.label,
              ref: item.field,
              ...item.editConfig,
              value: row[item.editConfig.field || item.field], // 若editConfig里有field，则以此field的值进行填充，否则按columns里的field值进行填充
              _valid: '' // 有效性
            };
            if (item.editConfig.rules) {
              this.rowModel[row._uid][item.field]._valid = checkRule(item.editConfig.rules, row[item.editConfig.field || item.field], row);
            }

            if (item.editConfig.type === 'image') { // 配合DetailTable里的popup使用
              this.rowModel[row._uid][item.field].forPopup = true;
            }
          }
        });
      } else { // 该数据行模型已存在，把最新字段值赋值给该行模型
        this.field.columns.forEach(item => {
          if (item.editConfig) {
            this.rowModel[row._uid][item.field].value = row[item.editConfig.field || item.field];
            if (item.editConfig.rules) {
              this.rowModel[row._uid][item.field]._valid = checkRule(item.editConfig.rules, row[item.editConfig.field || item.field], row);
            }
          }
        });
      }
      // console.log(this.rowModel);
    },
    canPopupEdit (col, row) {
      // 解析modal结果
      let customizeReadonly = !!(col.editConfig && col.editConfig.readonly);
      if (typeof this.field.modal == 'function') { // 检查预定义结果
        const customizeResult = this.field.modal('edit', row);
        if (customizeResult[col.field] && customizeResult[col.field].readonly !== undefined) {
          customizeReadonly = !!customizeResult[col.field].readonly;
        }
      }

      return col.editConfig && col.editConfig.type && this.field.editmode.includes('edit') && !customizeReadonly;
    },
    // 调整数据行顺序
    // row 数据行
    // flag f - 上移 b - 下移
    changeSort (idx, flag) {
      let uid1, uid2;
      function swapItems (arr, index1, index2) {
        uid1 = arr[index1]._uid;
        uid2 = arr[index1]._uid;
        arr[index1] = arr.splice(index2, 1, arr[index1])[0];
        return arr;
      };

      // 上移
      function upRecord (arr, index) {
        if (index == 0) {
          return;
        }
        swapItems(arr, index, index - 1);
      };

      // 下移
      function downRecord (arr, index) {
        if (index == arr.length - 1) {
          return;
        }
        swapItems(arr, index, index + 1);
      };

      let data = [];
      if (typeof this.field.value === 'string') {
        data = JSON.parse(this.field.value || '[]');
      } else {
        data = this.field.value;
      }

      if (flag === 'f') {
        upRecord(data, idx)
      } else {
        downRecord(data, idx);
      }
      this.field.value = data;
      this.$emit('change', { value: this.field.value, uid: [uid1, uid2], type: 'sort_' + flag }); // 通知改变
    },
    // 检查数据有效性
    validate () {
      const cols = this.field.columns.filter(v => v.editConfig && v.editConfig.rules);
      const error = [];
      for (let i = 0; i < this.tableData.length; i++) {
        const row = this.tableData[i];
        for (const col of cols) {
          for (const rule of col.editConfig.rules) {
            const err = rule(row[col.field], row);
            if (err !== true) {
              error.push(`第${i + 1}行 ${col.label} ${err}`);
            }
          }
        }
      }
      return {
        result: error.length > 0 ? 'fail' : 'ok',
        message: error.join(',')
      };
    },
    // 返回当前编辑行数据
    currentRowData (field) {
      const row = {};
      if (this.editFields) {
        Object.keys(this.editFields).forEach(key => {
          row[key] = this.editFields[key].value;
        });
      }
      return field ? row[field] : row;
    },
    footerColumns (item) {
      const itemFooter = [];
      for (let i = 0; i < item.length; i++) {
        if (i == 0) {
          itemFooter.push([item[i]]);
        } else if (item[i]) {
          itemFooter.push([this.columns[i].label, item[i]]);
        }
      }
      return itemFooter;
    },
    expandAll (flag) {
      if (flag) {
        this.expanded = this.tableData.map(v => v._uid);
      } else {
        this.expanded = [];
      }
    },
    canShowCompare (field) {
      /*
        compare的值
        compare: {
          label, // 标题
          keyField, // 定位数据的字段
          value, // 做对比数据集
          fields, // 可做对比的字段 不定义，表示全都对比，若定义，array型，例如：['field1', { oldField: newField}, ...]，各项数据，若为字符，则为该字段，若为键值对，key表示定义的字段名，value表示取数据的字段名，value还能为function
          always // true-有数据就显示 false-值不同，再显示
        } */
      return this.field.compare && this.field.compare.keyField &&
        (!this.field.compare.fields || this.field.compare.fields.map(v => typeof v === 'string' ? v : Object.keys(v)[0]).includes(field)) &&
        this.field.compare.value && this.field.compare.value.length > 0;
    },
    compare (row, field) { // 新老值对比
      let compareResult = '';

      const key = String(row[this.field.compare.keyField]);
      const compareRow = this.compareObj[key];

      let dataField = field;
      if (this.field.compare.fields) {
        const idx = this.field.compare.fields.findIndex(v => (typeof v === 'string' ? v : Object.keys(v)[0]) === dataField);
        const defineField = this.field.compare.fields[idx];
        if (typeof defineField === 'object') {
          dataField = defineField[field];
        }
      }
      let curValue, comValue;
      if (typeof dataField === 'function') {
        curValue = dataField(row);
        comValue = compareRow ? dataField(compareRow) : '';
      } else {
        curValue = row[dataField];
        comValue = compareRow ? compareRow[dataField] : '';
      }

      if (this.field.compare.always || compareRow && curValue != comValue) {
        compareResult = `<div class="text-white bg-yellow-10">${this.field.compare.label ? this.field.compare.label + '：' : ''}${comValue}</div>`;
      } else if (compareRow === undefined) {
        compareResult = '<div class="bg-red-8 text-white">新增内容</div>';
      }
      return compareResult;
    },
    additionalBtn (btnCfg) {
      if (Object.prototype.toString.call(btnCfg) === '[object Array]') {
        return btnCfg;
      } else {
        return [btnCfg]
      }
    }
  },
  computed: {
    ...mapState(useStore, ['dictionary']),
    tableData () { // 解析表数据为json格式，为每行数据添加_uid，并建立行模型
      let data = [];
      // 接收string及object两种格式的数据
      if (typeof this.field.value === 'string') {
        data = JSON.parse(this.field.value || '[]') || [];
      } else {
        data = this.field.value || [];
      }

      for (const d of data) {
        if (!d._uid) {
          d._uid = uid();
        }
        this.initRowModel(d);
      }
      // console.log(this.rowModel);
      return data
    },
    columns () {
      const col = [...this.field.columns.filter(v => v.visible !== false)];
      if (this.field.editmode.includes('edit') || this.field.editmode.includes('delete')) {
        col.unshift({ name: 'operate', label: '操作', align: 'center', headerStyle: 'width: 80px' });
      }
      return col;
    },
    changeRender () { // 取出有dataRender项的数据变动
      const change = [];
      for (const key in this.editFields) {
        if (this.editFields[key].dataRender) {
          change.push([key, this.editFields[key].value]);
        }
      }
      return JSON.stringify(change);
    },
    selection () {
      return this.field.selection || 'none';
    },
    itemSelectAll: {
      get () {
        let result = null;
        if (this.field.selected.length === this.tableData.length) {
          result = true;
        } else if (this.field.selected.length === 0) {
          result = false;
        }
        return result;
      },
      set (val) {
        if (val) {
          this.field.selected = this.tableData;
        } else {
          this.field.selected = [];
        }
      }
    },
    storageKey () {
      const path = this.$route.path.split('/');
      let word = path.length > 2 ? path[2] : path[path.length - 1];
      word = `table_cols_${word}_${this.field.visibleId || ''}`;
      return word;
    },
    compareObj () { // 把对比的数据转成对象
      if (this.field.compare && this.field.compare.value && this.field.compare.value.length > 0 && this.field.compare.keyField) {
        const compareValue = typeof this.field.compare.value === 'string' ? JSON.parse(this.field.compare.value) : this.field.compare.value;
        const obj = {};
        for (const row of compareValue) {
          const key = String(row[this.field.compare.keyField]);
          obj[key] = row;
        }
        return obj
      } else {
        return {};
      }
    }
  },
  watch: {
    tableData (val) {
      this.dataset = val;
    },
    changeRender (newVal, oldVal) {
      if (newVal != '[]') {
        const changed = [];
        const newArr = JSON.parse(newVal);
        const oldObj = JSON.parse(oldVal);
        // 找到变动项
        if (newArr.length === oldObj.length) { // 第二次后变动
          for (let i = 0; i < newArr.length; i++) {
            newArr[i][1] != oldObj[i][1] && changed.push(newArr[i][0]);
          }
          // 对变动项执行数据渲染函数
          for (const key of changed) {
            const changedField = this.editFields[key];
            if (changedField.type === 'select') {
              this.$nextTick(() => {
                changedField.dataRender(this.editFields, changedField.selected);
              });
            } else {
              if (changedField._canRender) {
                changedField.dataRender(this.editFields, changedField.value);
              }
            }
          }
        } else if (oldObj.length === 0) { // 执行前置数据渲染
          for (const f in this.editFields) {
            if (this.editFields[f].preRender) {
              const changedField = this.editFields[f];
              if (changedField.type === 'select') {
                this.$nextTick(() => {
                  changedField.dataRender(this.editFields, changedField.selected);
                });
              } else {
                changedField.dataRender(this.editFields, changedField.value);
              }
            }
          }
        }
      }
    },
    visibleColumns (val) {
      this.$q.localStorage.set(this.storageKey, val);
    },
    columns () {
      if (this.field.visibleId && this.$q.localStorage.getItem(this.storageKey)) {
        const allColName = this.columns.map(v => v.name);
        const visCols = this.$q.localStorage.getItem(this.storageKey);
        if (allColName.includes('operate') && !visCols.includes('operate')) {
          visCols.push('operate');
        }
        this.visibleColumns = visCols;
      } else {
        this.visibleColumns = this.columns.map(v => v.name);
      }
    },
    'field.value' (val) {
      this.rowModel = {};
      this.expanded = [];
    },
    expanded (val) {
      if (!this.field.expanded) {
        this.field.expanded = [];
      }
      this.field.expanded.length = 0;

      const expandKey = this.field.expandKey || 'id';

      for (let i = 0; i < val.length; i++) { // 此方法不会引起数组watch，防止死循环，
        this.field.expanded[i] = String(this.rowModel[val[i]][expandKey].value);
      }
    },
    'field.expanded' (val) {
      const expandKey = this.field.expandKey || 'id';
      if (val && val.length > 0) {
        const expanded = [];
        const fieldExpanded = val.map(v => String(v));

        for (const _uid in this.rowModel) {
          if (fieldExpanded.includes(String(this.rowModel[_uid][expandKey].value))) {
            expanded.push(_uid);
          }
        }
        this.expanded = expanded;
      }
    }
  }
});
</script>

<style lang="scss" scoped>
.valid {
  color: $red;
  font-size: 0.6rem
}

.compare {
  margin-top: 8px;
  font-size: 12px;
}
</style>
